using System;
using System.Collections.Generic;
using System.IO;

namespace SharpFastCGI.Internals
{
	/// <remarks>
	/// The implementation does not care about request ID regarding the params stream.
	/// </remarks>
	internal class InputBuffer // TODO
	{
		private delegate bool ContentHandler();
		
		private System.Text.Encoding mEncoding = System.Text.Encoding.ASCII;
		private Stream mStream;
		
		private byte[] mBuffer;
		private int mOffset;
		private int mFirstInvalidOffset;
		
		// Header Parsing State
		private byte mRecordType;
		private ushort mRecordId;
		private ushort mRecordContentLength;
		private byte mRecordPaddingLength;
		
		private int mReadParams_state;
		private int mReadParams_read;
		private char[] mReadParams_chars;
		private int mReadParams_keyLength;
		private int mReadParams_valueLength;
		private string mReadParams_key;
		private string mReadParams_value;
		
		private ContentHandler[] mContentHandlers;
		private IRecordHandler mRecordHandler;
		
		private List<string> mReadGetValues_keys;
		private StreamReaderImpl mStreamReader;
		
		public InputBuffer(int BufferSize, IRecordHandler RecordHandler)
		{
			BufferSize += 8 - (BufferSize % 8);
			mBuffer = new byte[BufferSize];
			
			mContentHandlers = new ContentHandler[(int)RecordType.Unknown + 1];
			mContentHandlers[(int)RecordType.AbortRequest] = ReadAbortRequest;
			mContentHandlers[(int)RecordType.BeginRequest] = ReadBeginRequest;
			mContentHandlers[(int)RecordType.Data] = ReadData;
			mContentHandlers[(int)RecordType.GetValues] = ReadGetValues;
			mContentHandlers[(int)RecordType.Params] = ReadParams;
			mContentHandlers[(int)RecordType.Stdin] = ReadStdin;
			mContentHandlers[(int)RecordType.Unknown] = ReadUnknown;
			
			mRecordHandler = RecordHandler;
			
			mReadGetValues_keys = new List<string>(10);
			mStreamReader = new StreamReaderImpl();
		}
		
		public void Initialize(Stream Input)
		{
			mStream = Input;
			mReadParams_state = 0;
			mReadParams_read = 0;
		}
		
		public RecordType WaitBeginRequestRecord()
		{
			
		}
		
		public void WaitEndOfStreams()
		{
			
		}
		
		/// <summary>
		/// Processes the next record from the Stream.
		/// </summary>
		/// <returns>
		/// <b>True><b> if the record is valid, <b>False</b> otherwise.
		/// </returns>
		private bool ProcessRecord()
		{
			mOffset = mFirstInvalidOffset = 0;
			
			if(!ReadRecordHeader()) return false;
			
			ContentHandler hdlr;
			if(mRecordType >= mContentHandlers.Length || (hdlr = mContentHandlers[(byte)mRecordType]) == null)
			{
				Write_UnknownTypeRecord(mRecordType);
				return ToSkip(mRecordContentLength + mRecordPaddingLength);
			}
			
			return hdlr() && ToSkip(mRecordPaddingLength);
		}
		
		private bool ReadRecordHeader()
		{
			if(!FillBuffer(8)) return false;
			
			int o = mOffset;
			// Version
			if(mBuffer[o+0] != FCGI_VERSION_1) return false;
			// Type
			mRecordType = mBuffer[o+1];
			// Request Id
			mRecordId = ((ushort)mBuffer[o+2] << 8) | (ushort)mBuffer[o+3];
			// Content Length
			mRecordContentLength = ((ushort)mBuffer[o+4] << 8) | (ushort)mBuffer[o+5];
			// Padding Length
			mRecordPaddingLength = mBuffer[o+6];
			
			mOffset += 8;
			return true;
		}
		
		private bool ReadBeginRequest()
		{
			if(!FillBuffer(8)) return false;
			
			int o = mOffset;
			ushort role = ((ushort)mBuffer[o] << 8) | (ushort)mBuffer[o+1];
			byte flags = mBuffer[o+2];
			
			mOffset += 8;
			
			mRecordHandler.BeginRequest(mRecordId, (BeginRequestFlags)flags, (Role)role);
			
			return true;
		}
		
		private bool ReadAbortRequest()
		{
			mRecordHandler.AbortRequest(mRecordId);
			
			return true;
		}
		
		private bool ReadGetValues()
		{
			int left = mRecordContentLength;
			List<string> keys = mReadGetValues_keys;
			keys.Clear();
			
			while(left > 0)
			{
				int nameLen, valueLen;
				mOffset = 0;
				
				//left -= mOffset;
				if(!ReadNameValueLengths(out nameLen, out valueLen)) return false;
				if(valueLen != 0) return false;
				left += mOffset;
				
				string s = ReadString(nameLen);
				if(s == null) return false;
				keys.Add(s);
			}
			
			mRecordHandler.GetValues(mRecordId, keys);
			
			return true;
		}
		
		private bool ReadParams()	// This is a stream
		{
			if(mRecordContentLength == 0)
			{
				mRecordHandler.Params_EndOfStream(mRecordId);
				return true;
			}

			do
			{
				switch(mReadParams_state)
				{
					case 0: // read key length
					{
						if(!ReadParams_ReadLength(ref mReadParams_keyLength, 1)) return false;
					}
					
					case 1: // read value length
					{
						if(!ReadParams_ReadLength(ref mReadParams_valueLength, 2)) return false;
					}
					
					case 2: // read key
					{
						if(!ReadParams_ReadString(ref mReadParams_key, mReadParams_keyLength, 3)) return false;
					}
					
					case 3: // read value
					{
						if(!ReadParams_ReadString(ref mReadParams_value, mReadParams_valueLength, 4)) return false;
					}
					
					case 4:
					{
						mRecordHandler.Params(mRecordId, mReadParams_key, mReadParams_value);
						mReadParams_state = 0;
					}
				}
			} while(mRecordContentLength > 0);
		}
		
		private bool ReadParams_ReadLength(ref int Length, int NextState)
		{
			if(mReadParams_read == 0)
			{
				FillBuffer(1);
				--mRecordContentLength;
				if(Length = mBuffer[mOffset++] <= 127) goto nextState;
				++mReadParams_read;
			}
		
			int i = Math.Min(3, mRecordContentLength);
			FillBuffer(i);
			mRecordContentLength -= i;
			
			while(mRecordContentLength > 0)
			{
				Length = (Length << 8) | mBuffer[mOffset++];
				if(++mReadParams_read == 4) goto nextState;
			}
			
			return;
			
		 nextState:
			mReadParams_state = NextState;
			mReadParams_read = 0;
		}
		
		private bool ReadParams_ReadString(ref string String, ref int Length, int NextState)
		{
			if(mReadParams_read == 0)
			{
				if(Length <= mRecordContentLength)
				{
					String = ReadString(Length);
					if(String == null) return false;
					mRecordContentLength -= Length;
					goto nextState;
				}
				else
				{
					mReadParams_chars = new char[Length];
				}
			}
			
			int left = Length - mReadParams_read;
			int read = Math.Min(mRecordContentLength, left);
			if(!ReadChars(mReadParams_chars, mReadParams_read, read)) return false;
			mRecordContentLength -= read;
			mReadParams_read += read;
			if(read == left)
			{
				String = new string(mReadParams_chars);
				goto nextState;
			}
			
			return;
			
		 nextState:
			mReadParams_state = NextState;
			mReadParams_read = 0;
		}
		
		private bool ReadStdin()	// This is a stream
		{
			if(mRecordContentLength == 0)
			{
				mRecordHandler.Stdin_EndOfStream(mRecordId);
				return true;
			}
			
			mStreamReader.Initialize(mStream, mRecordContentLength);
			mRecordHandler.Stdin(mRecordId, mRecordContentLength, mStreamReader);
			if(mStreamReader.mStreamClosed) return false;
			if(mStreamReader.mDataLeft > 0) return SkipData(mStreamReader.mDataLeft);
			return true;
		}
		
		private bool ReadData()	// This is a stream
		{
			if(mRecordContentLength == 0)
			{
				mRecordHandler.Data_EndOfStream(mRecordId);
				return true;
			}
			
			mStreamReader.Initialize(mStream, mRecordContentLength);
			mRecordHandler.Data(mRecordId, mRecordContentLength, mStreamReader);
			if(mStreamReader.mStreamClosed) return false;
			if(mStreamReader.mDataLeft > 0) return SkipData(mStreamReader.mDataLeft);
			return true;
		}
		
		private bool ReadUnknown()
		{
			if(!FillBuffer(8)) return false;
			
			int o = mOffset;
			byte type = mBuffer[o];
			
			mOffset += 8;
			
			mRecordHandler.Unknown(mRecordId, type);
			
			return true;
		}
		
		private bool ReadNameValueLengths(out int NameLength, out int ValueLength)
		{
			if(!FillBuffer(1)) return false;
			
			int o = mOffset;
			
			if(mBuffer[o] > 127)
			{
				if(!FillBuffer(4)) return false;
				NameLength = (((int)mBuffer[o] & 0x7F) << 24) | ((int)mBuffer[o+1] << 16) | ((int)mBuffer[o+2] << 8) | (int)mBuffer[o+3];
				o += 4;
			}
			else
			{
				NameLength = (int)mBuffer[o];
				++o;
			}
			
			if(mBuffer[o] > 127)
			{
				if(!FillBuffer(4)) return false;
				ValueLength = (((int)mBuffer[o] & 0x7F) << 24) | ((int)mBuffer[o+1] << 16) | ((int)mBuffer[o+2] << 8) | (int)mBuffer[o+3];
				o += 4;
			}
			else
			{
				ValueLength = (int)mBuffer[o];
				++o;
			}
			
			return true;
		}
		
		private bool ReadChars(char[] Array, int Offset, int Length)
		{
			while(Length > 0)
			{
				int read = mStream.Read(mBuffer, 0, Math.Min(mBuffer.Length, Length));
				if(read == -1) return false;
				mEncoding.GetChars(mBuffer, 0, read, Array, Offset);
				Length -= read;
				Offset += read;
			}
			return true;
		}
		
		private string ReadString(int Length)
		{
			if(Length > mBuffer.Length)
			{
				char[] chars = new char[Length];
				if(!ReadChars(chars, 0, Length)) return null;
				return new string(chars); // TODO: this will copy the chars, use char* instead?
			}
			
			mOffset = 0;
			FillBuffer(Length);
			return mEncoding.GetString(mBuffer, 0, Length);
		}
		
		private bool SkipData(int ToSkip)
		{
			while(ToSkip > 0)
			{
				int read = mStream.Read(mBuffer, 0, Math.Min(mBuffer.Length, ToSkip));
				if(read == 0) return false;
				ToSkip -= read;
			}
			return true;
		}
		
		private bool FillBuffer(int Length)
		{
			int toget;
			while((toget = Length - (mFirstInvalidOffset - mOffset)) > 0)
			{
				int read = mStream.Read(mBuffer, mValidUpTo + 1, toget);
				if(read <= 0) return false;
				mFirstInvalidOffset += read;
			}
			return true;
		}
		
		private class StreamReaderImpl : InputStream
		{
			public Stream mStream;
			public int mDataLeft;
			public bool mStreamClosed;
			
			public bool IsClosed
			{
				get { return mStreamClosed || mDataLeft == 0; }
			}
			
			public void Dispose()
			{
				
			}
			
			public void Initialize(Stream Stream, int DataLength)
			{
				mStream = Stream;
				mStreamClosed = false;
				mDataLeft = DataLength;
			}
			
			public int Read(byte[] Buffer, int Offset, int Length)
			{
				if(mDataLeft == 0) return 0;
				int read = mStream.Read(Buffer, Offset, Math.Min(Length, mDataLeft));
				if(read == 0) mStreamClosed = true;
				mDataLeft -= read;
				return read;
			}
		}
	}
}
